package main

import (
	"fmt"
	"io"
	"os"
	"reflect"
)

func main() {
	/*
		总结：
		1、基于反射的代码是及其脆弱的，反射中的类型错误会在 真正运行 的时候才会引发 panic，那很可能是在代码写完的很长时间之后。
		2、大量使用反射的代码通常难以理解
		3、反射性能低下，基于反射实现的代码通常比正常代码运行速度慢一到两个数量级。
	*/

	/*
		1.需要反射的原因：
			我们没有办法检查未知类型的表示方法
		2.reflect.Type 和 reflect.Value
			函数 reflect.TypeOf 接收任意类型的 interface{} 类型，并以 reflect.Type 形式返回动态类型
	*/
	t := reflect.TypeOf(3)
	fmt.Println(t.String())       //int
	fmt.Println(t)                //int
	fmt.Printf("%T   %v\n", t, t) //*reflect.rtype   int
	/*
		其中 TypeOf(3)调用将值 3 传给 interface{} 参数。
		将一个具体的值转为 接口类型会有一个隐式的接口转换操作，它会创建一个包含两个信息的接口值：操作数的动态类型和它的动态的值（这里是3）
	*/
	/*
		因为 reflect.TypeOf 返回的是一个动态类型的接口值，它总是返回具体的类型。因此，下面的代码将打印 "*os.File" 而不是 "io.Writer"
	*/
	var w io.Writer = os.Stdout
	fmt.Println(reflect.TypeOf(w)) //*os.File

	/*
		要注意的是 reflect.Type 接口是满足 fmt.Stringer 接口的。
		因为打印一个接口的动态类型对于调试和日志是有帮助的，fmt.Printf 提供了一个缩写 %T 参数，内部使用 reflect.Typeof 来输出。
	*/
	fmt.Printf("%T\n", 3) //int

	/*
		reflect 包的另一个重要的类型是 Value。一个 reflect.Value 可以装在任意类型的值。
		函数 reflect.ValueOf 接受任意的 interface{}类型，并返回一个装载着其动态值的reflect.Value。
		和 reflect.TypeOf 类似，reflect.ValueOf 返回的结果也是具体的类型，
		但是 reflect.Value 也可以持有一个接口值。
	*/
	v := reflect.ValueOf(3)
	fmt.Println(v)                  //3
	fmt.Printf("%T     %v\n", v, v) //reflect.Value     3
	fmt.Println(v.String())         //<int Value>
	/*
		和 reflect.Type 类似，reflect.Value 也满足 fmt.Stringer 接口，
		但是除非 Value 持有的是字符串，否则 String 方法只返回其类型。
		而使用 fmt包 的 %v 标志参数会对 reflect.Values 特殊处理
	*/

	/*
		对 Value 调用 Type 方法将会返回具体类型所对应的 reflect.Type
	*/
	x := v.Type()
	fmt.Printf("%T\n", x)   //*reflect.rtype
	fmt.Println(x.String()) //int

	fmt.Println("-----------------------------------------------------------")

	/*
		reflect.ValueOf 的 逆操作 是 reflect.Value.Interface 方法。
		它返回一个 interface{} 类型，装载着与 reflect.Value 相同的具体值：
	*/
	v1 := reflect.ValueOf(3)
	x1 := v1.Interface()
	i := x1.(int)
	fmt.Printf("%d\n", i) //3
	/*
		reflect.Value 和 interface{} 都能装载任意的值。
		所不同的是，一个空的接口隐藏了值内部的表示方式和所有方法，因此只有 知道具体的动态类型 才能使用 类型断言 类访问内部的值，但内部值没法访问。
		相比之下，一个 Value 则有很多方法来检查其内容，无论它的具体类型是什么。
	*/

	fmt.Println("--------------------------通过 reflect.Value修改值-----------------------------")

	/*
		一个变量就是一个可寻地址的内存空间，里面存储了一个值，并且存储的值可以通过内存地址来更新，
		对于 reflect.Values 也有类似的区别，有一些 reflect.Values 是可取地址的；其他的一些则不可以
	*/
	x0 := 2
	a := reflect.ValueOf(2)
	b := reflect.ValueOf(x0)
	c := reflect.ValueOf(&x0)
	d := c.Elem()
	/*
		其中 a 对应的变量是不可取地址的，因为 a 中的值仅仅是 整数2 的 拷贝副本。
		b 中的值也是不可取地址。
		c 中的值还是不可取地址，它只是一个指针 &x 的拷贝。
		但是对于 d，它是 c 的解引用方式生成的，指向另一个变量，因此是可取地址的。
	*/
	/*
		实际上，所有通过reflect.ValueOf(x) 返回的 reflect.Value 都是不可取地址的。ஐஐஐஐஐஐஐஐஐஐஐஐஐ✈✈✈✈✈
		每当我们通过 指针 间接地获取的 reflect.Value 都是可取地址的，即使开始的是一个不可取地址的 Value。
		在反射机制中，所有关于是否支持取地址的规则都是类似的。
		例如，slice 的索引表达式 e[i] 将隐式地包含一个指针，它就是可取地址的，即使开始的 e 表达式不支持也没有关系
		1、reflect.ValueOf(e).Index(i) 对应的值也是可以取地址的，即是原始的 reflect.Value(e) 不支持也没关系。
		2、我们可以通过调用 reflect.ValueOf(&x).Elem()，来获取任意变量 x 对应的可取地址的 Value。
		3、我们可以通过调用 reflect.Value 的 CanAddr 方法来 判断其是否可以被取地址
	*/
	fmt.Println(a.CanAddr()) //false
	fmt.Println(b.CanAddr()) //false
	fmt.Println(c.CanAddr()) //false
	fmt.Println(d.CanAddr()) //true

	/*
		从变量对应的可取地址的 reflect.Value 来访问变量的步骤：
			1.调用 Addr() 方法，它返回一个 Value，里面保存了指向变量的指针。
			2.调用在 Value 上的 interface() 方法，也就是返回一个 interface{}，里面包含指向变量的指针。
			3.如果我们知道变量的类型，我们可以使用类型的 断言机制 将得到的 interface{} 类型的接口 强制转换为普通的类型指针。
			这样我们就可以通过这个普通指针来更新变量了。
	*/
	x2 := 2
	d2 := reflect.ValueOf(&x2).Elem()  //d2 refers to the variable x
	px := d2.Addr().Interface().(*int) //px := &x
	*px = 3                            //x = 3
	fmt.Println(x2)                    //3

	/*
		或者，不使用指针，而是通过调用可取地址的 reflect.Value 的 reflect.Value.Set 方法来 更新对应的值

		Set方法 将在运行时 执行和编译时 进行 类比的可赋值性约束的检查。
		所以，关键问题是要 确保被修改类型的变量可以接收对应的值。
			例如，一个是 int32类型，一个是 int64 类型，程序则会抛出一个 panic
		同样，对于一个不可取地址的 reflect.Value调用 Set方法 也会导致 panic

		基于基本数据类型的 Set方法：SetInt、SetUint、SetString、SetFloat 等
		从某种角度来说，这些 Set方法总是尽可能的完成任务。
			以SetInt为例，只要变量是某种类型的有符号整数就可以工作，
		   即使是一些命名的类型、甚至只要底层数据类型是有符号整数就可以，而且如果对于变量类型值太大的话会被自动截断。
		   但需要谨慎的是：对于一个引用**interface{}**类型的reflect.Value调用SetInt会导致panic异常，即使那个interface{}变量对于整数类型也不行。

	*/
	d2.Set(reflect.ValueOf(4))
	fmt.Println(x2) //4
	d2.SetInt(5)
	fmt.Println(x2) //5

	/*
		反射可以越过Go语言的导出规则的限制读取结构体中未导出的成员，比如在类Unix系统上os.File结构体中的fd int成员。
		然而，利用反射机制并不能修改这些未导出的成员：（用unsafe直接操作指针可以）
		一个可取地址的 reflect.Value 会记录一个结构体成员是否是未导出成员，如果是的话则拒绝修改操作。
		因此，CanAddr方法并 不能正确反映 一个变量是否是可以被修改的。
		CanSet 方法，是用于 检查对应的 reflect.Value 是否是可取地址并可被修改的
	*/

}
